#include <pthread.h>

#include "utils.h"

double **x, **xn; //source and dest

void* worker(void *data);

int main(int argc, char *argv[])
{
	/* check programm call */
	if (6 != argc){
		usage();
	}

#if DEBUG == 1
	char color[50];
#endif

	unsigned int i,j,k;
	unsigned long runtime;

	struct timeval start;
	struct timeval end;
	
	double deg, rad;
	unsigned int origin;
	int di, dj;
	
	//configuration
	unsigned int iterations = atoi(argv[1]);
	unsigned int viewsize = atoi(argv[2]);
	unsigned int heatspot = atoi(argv[3]);		// => m
	double heatval = atof(argv[4]);			// => H
	unsigned int threads = atoi(argv[5]);
	
	//thread
	pthread_t *thread;
	thread_data *data;
	unsigned int workload = 0;
	int viewBuffer = viewsize;
	int offset = 0;


	/* sanity check */
	if (iterations < 0 || iterations > 1e6) usage();
	if (viewsize < 0 || viewsize > 1e6) usage();
	if (heatspot < 0 || heatspot >= viewsize) usage();
	if (heatval < 0.0f || heatval > 127.0f) usage();
	if (threads == 0 || threads > MAX_THREADS) usage();

	/* adjust dimension to fit the desired size */
	unsigned int worksize = viewsize + 4;
	
	/* allocate memory */
	double **swp, **circle;
	x = (double**) malloc(worksize * sizeof(double*));
	xn = (double**) malloc(worksize * sizeof(double*));
	circle = (double**) malloc(worksize * sizeof(double*));
	
	thread = (pthread_t*)malloc(threads * sizeof(pthread_t));
	data = (thread_data*)malloc(threads * sizeof(thread_data));
	
	for (i=0; i<worksize; i++) 
		x[i] = (double*) malloc(worksize * sizeof(double));
	for (i=0; i<worksize; i++)
		xn[i] = (double*) malloc(worksize * sizeof(double));
	for (i=0; i<worksize; i++) 
		circle[i] = (double*) malloc(worksize * sizeof(double));

	/* init arrays */
	for (i=0; i<worksize; i++) 
		for (j=0; j<worksize; j++)
		{
			x[i][j] = 0.0f;
			xn[i][j] = 0.0f;
			circle[i][j] = 0.0f;
		}

	/* set initial state of heat spot */
	origin = worksize/2;
	for (i=0; i<heatspot; i++)
	{
		/* sweep all radii */
		for (deg = 0; deg <= 360; deg += 0.5f)
		{
			DEG2RAD(deg, rad);
			//printf("%lf %lf\n", deg, rad);
			di = origin + sin(rad)*i;
			dj = origin + cos(rad)*i;
			//printf("%i %i\n", di, dj);
			circle[di][dj] = heatval;
		}
	}

	//Step 1: calculate workload for each thread
	if(viewsize % threads != 0){	
		workload = viewsize / threads + 1;
	}else{
		workload = viewsize / threads;
	}
	printf("#Workload for each thread %d viewBuffer %d\n",workload,viewBuffer);
	
	offset = 2;
	for(i = 0; i < threads; i++){
		(data+i)->tid = i;

		int diff = viewBuffer - workload;

		if((viewBuffer > 0) && (diff > 0)){
			(data+i)->dimY = workload;
			viewBuffer -= workload;
#if DEBUG == 1
			printf("1. ViewBuffer: %d\n", viewBuffer);
#endif
		}else if(viewBuffer > 0){
			(data+i)->dimY = viewBuffer;
#if DEBUG == 1
			printf("2. ViewBuffer: %d\n", viewBuffer);
#endif
			viewBuffer = 0;
		}else{
			threads = i;
			printf("#WARNING: To many threads are used. Can't distribute workload in a fair way. Will use %d threads.\n\n",threads);
			break;
		}

		(data+i)->offsetY = offset;
		offset += (data+i)->dimY;
#if DEBUG == 1
		printf("#thread %d : dimY %d , offset %d\n",(data+i)->tid, (data+i)->dimY, (data+i)->offsetY);
#endif
		(data+i)->dimX = worksize;
	}
	
	/* benchmark */
	printf("#Will start %d threads",threads);
	gettimeofday(&start, NULL);
	for (k=0; k<iterations; k++){
		/* keep heat source active */
		for (i=2; i<worksize-2; i++){
			for (j=2; j<worksize-2; j++){
				if (circle[i][j] != 0.0){
					x[i][j] = circle[i][j];
				}
			}
		}

		//Step 2: Create Threads:
		for(i = 0; i < threads; i++){	
			pthread_create((thread+i),NULL,worker,(void*)(&data[i]));
		}
	
		//step 3: Synchronise before the next iteration begins:
		for(i = 0; i < threads; i++){
			pthread_join(*(thread+i),NULL);
		}

#if DEBUG == 1
		// uncomment to print output
		for (i=2; i<worksize-2; i++)
		{
			for (j=2; j<worksize-2; j++)
			{
				double *cv = &x[i][j]; // current value

				if (*cv >= 0.0 && *cv < 18.0)
					strcpy(color, KWHT);
				else if (*cv > 18.0 && *cv < 36.0)
					strcpy(color, KCYN);
				else if (*cv > 36.0 && *cv < 54.0)
					strcpy(color, KMAG);
				else if (*cv > 54.0 && *cv < 72.0)
					strcpy(color, KBLU);
				else if (*cv > 72.0 && *cv < 90.0)
					strcpy(color, KGRN);
				else if (*cv > 90.0 && *cv < 108.0)
					strcpy(color, KYEL);
				else if (*cv > 108.0 && *cv <= 127.0)
					strcpy(color, KRED);
				else
					strcpy(color, KNRM);

				//printf("%.1lf ", x[i][j]);
				printf("%s# ", color);
			}
			printf("\n");
		}
		printf("iteration: %i/%i\n", k+1, iterations);
		usleep(1e3*50); // ms
		printf("\n\f");
#endif

		swp = xn;
		xn = x;
		x = swp;
		swp = NULL;
	}
	gettimeofday(&end, NULL);

	/* free memory */
	for (i=0; i<worksize; i++)
	{
		free(x[i]);
		free(xn[i]);
		free(circle[i]);
	}
	free(x);
	free(xn);
	free(circle);

	free(thread);
	free(data);	

	runtime = calcRuntime(start,end);
	printf("%i %i %lf %lf threads: %d\n",
		viewsize, iterations, (double)runtime/1e6, (double)runtime/1e6/iterations,threads);

	return 0;
}


void* worker(void *data){
thread_data *dat = (thread_data*)(data);
//Do calculations:
int xSize = dat->dimX;
int ySize = dat->dimY+dat->offsetY;

int i = 0;
int j = 0;
#if DEBUG == 1
	printf("#Thread %d : xSize %d , ySize %d\n",dat->tid,xSize,ySize);
#endif

		for (i=dat->offsetY; i<ySize; i++)
		{
			for (j=2; j< xSize; j++)
			{
				double *cv = &x[i][j]; // current value
				double *cvd = &x[i+1][j]; // cv-down 
				double *cvu = &x[i-1][j]; // cv-up
				double *cvr = &x[i][j+1]; // cv-right
				double *cvl = &x[i][j-1]; // cv-left

				xn[i][j] = *cv + PHI * (-4.0f * *cv + *cvd + *cvu + *cvr + *cvl);
			}
		}
return NULL;
}
